
using System;
using System.Collections.Generic;
using LibSars;
using System.Threading;
using System.Windows.Forms;
using System.Net.Sockets;


namespace SarsClient
{

	/// <summary>
	/// Class for managing connections.
	/// Core component of SARSClient.
	/// </summary>
	public class ConnectionManager {
			
		#region Class members. 
		/// <summary>List of received, but not processed packets.</summary>
		Queue<SarsPacket> receivedPackets = new Queue<SarsPacket>();
		
		/// <summary>Hashmap of peers.</summary>
		Dictionary<UInt16, Peer> peers = new Dictionary<UInt16, Peer>();
			
		// List with all registered MessageHandlers
		List<MessageHandler> MessageHandlers = new List<MessageHandler>();
		readonly UInt16 ownId;
		
		#endregion
		
		#region Private methods. 
		
		/// <summary>Sends a packet to a specific Peer.</summary>
		/// <param name="rec"> Address (ID) of the recipient. </param>
		/// <param name="packet"> Packet to send. </param>
		/// <param name="recList"> List of already tried Peers / Connections. </param>
		void SendPacket(UInt16 rec, ref SarsPacket packet, ref List<UInt16> recList){
			
			if (
				recList.Contains(rec) ||
				!peers.ContainsKey(rec) ||
				(recList.Count > 0 && rec == 0x0000)
			)
				return ;
			
			
			if (peers[rec].IsDirectlyConnected){
				Logging.Logger.DebugFormat("Send (final): {0}",packet.RecipientId);
			
				peers[rec].SendPacket(packet);
			}
			else {
				recList.Add(rec);
				SendPacket(peers[rec].AddedBy, ref packet, ref recList);
			}
			
		}
		
		
		#endregion
		
		#region Public methods.
		
		/// <summary>Sends a packet.</summary>
		/// <param name="packet">Packet to send.</param>
		public void SendPacket(SarsPacket packet){
			
			List<UInt16> recList = new List<UInt16>();
			SendPacket(packet.RecipientId, ref packet, ref recList);
		}
		
		
		
		
		
		/// <summary>Receive packets of all peers and put them in received packet list.</summary>
		public void ReceivePackets(){
			// TODO: real implementation
			//Iterates through list of peers,
			// generates new packet objects from PeerHandler and
			// enques them in receivedPackets.
			/* 
			 * DEMO tests
			 */
			try {
				List<Peer> peersCopy = new List<Peer>();
				lock(peers){
					foreach(Peer ptmp in peers.Values){
						peersCopy.Add(ptmp);
					}
					//Logging.Logger.DebugFormat("Number of copied peers (rec): {0}", peersCopy.Count);
				}

				foreach(Peer p in peersCopy){
	
	                SarsPacket packet = p.ReceivedPacket();
	
					if (packet != null)
						receivedPackets.Enqueue(packet);
					
	
				}
			}catch { }
			

		}
		
		public void ListUpdateThread(){
			while(true){
				try {
					List<Peer> peersCopy = new List<Peer>();
					lock(peers){
						foreach(Peer ptmp in peers.Values){
							peersCopy.Add(ptmp);
						}
						//Logging.Logger.DebugFormat("Number of copied peers (LU): {0}", peersCopy.Count);
					}

					
					SarsPacket packetOut = new SarsPacket();
					packetOut.SenderId = ownId;
					SarsPacketMessageUHASCL listMessage = new SarsPacketMessageUHASCL();
					List<SarsPeerItem> list = new List<SarsPeerItem>();
					foreach(Peer p2 in peersCopy){
						list.Add(p2.GetPeerItem());
					}
					listMessage.PeerList = list.ToArray();
					packetOut.Message = listMessage;
					
					
					foreach(Peer p in peersCopy){
						packetOut.RecipientId = p.Id;
		               	this.SendPacket(packetOut);
					}
				}catch { }
				Thread.Sleep(1000);
			}

		}
		
		/// <summary>Processes all enqueued packets in receivedPackets.</summary>
		public void ProcessReceivedPackets(){
			while(receivedPackets.Count > 0){
				SarsPacket p = receivedPackets.Dequeue();
				foreach(MessageHandler handler in MessageHandlers){
					if (handler.Handle(p,this))
						break ;
					// Corresponding message handler found and packet
					// handled, hence break.
				}
			}
		}
		
		public Peer[] GetAllPeers(){
			Peer[] array = new Peer[peers.Count];
			int idx = 0;
			foreach(Peer p in peers.Values){
				array[idx] = p;
				++idx;
			}
			return array;
		}
		
		/// <summary>Add Peer to hashmap of peers.</summary>
		public void AddPeer(Peer peer){
			// perform verification of peer
			
			// can't add self
			if(peer.Id == this.ownId){
				Logging.Logger.DebugFormat("Can't add self ({0}) to peer list",peer.Id);
				return ;
			}
			if (peers.ContainsKey(peer.Id)){
				Logging.Logger.DebugFormat("Peer {0} already in list",peer.Id);
				if (peers[peer.Id].IsDirectlyConnected){
					Logging.Logger.DebugFormat("Can't add Peer {0}: direct connect!",peer.Id);
					return ;
				}
				Logging.Logger.DebugFormat("Peer {0}: NOT directly connected!",peer.Id);
				peers.Remove(peer.Id);
			} else
				Logging.Logger.DebugFormat("Peer {0} NOT in list!",peer.Id);
			
			{
				// Add Peer
				peers.Add(peer.Id,peer);
				// Log debug message
				Logging.Logger.DebugFormat("Peer added: {0}",peer.Id);
			}
			/*
			SarsPacket p = new SarsPacket();
			p.SenderId = ownId;
			p.RecipientId = peer.Id;
			p.Message = new SarsPacketMessageSAITHIS("bla");
			peer.SendPacket(p);
			*/
	
		}
		
		///<summary>Return own user id (readonly!).</summary>
		/// <returns>User ID of this user.</returns>
		public UInt16 OwnId {
			get { return ownId; }
		}
		
		
		///<summary>Register new message handler.</summary>
		public void RegisterMessageHandler(MessageHandler handler){
			MessageHandlers.Add(handler);
			
		}
		
		///<summary>Constructor for connection manager. ID of current (connected
		/// user must be passed and be valid.</summary>
		/// <param name="p_ownId">ID of this user.</param>
		public ConnectionManager(UInt16 p_ownId){
			if (p_ownId == 0x0000 || p_ownId == 0xFFFF)
				throw new InvalidOperationException();
			ownId = p_ownId;
			//RegisterMessageHandler(new MessageHandlerROUTING());
			RegisterMessageHandler(new MessageHandlerCLMERGE(this));
			
			
		}
		
		///<summary>Register new peer from IP address.</summary>
		/// <param name="ip">The IP address of new peer.</param>
		public void ConnectTo(System.Net.IPAddress ip){
			new PeerConnector(this).Connect(ip,Global.ConnectionPort);
		}
		
		#endregion
	}
}
